Amazon S3のCross-Region Replicationを使ってAWS Lambdaを発火させる
こんにちは、せーのです。今日は最近出た新機能を使ったちょっとした実験をご紹介します。
きっかけは黒帯
つい1ヶ月ほど前にS3に「Cross-Region Replication」という機能が追加されました。これはS3のオブジェクトを別のリージョンにレプリケーションさせる、という主にDR(災害時の復旧手段)の目的で使うことになるであろう便利な機能です。Cross-Region Replicationについて詳しくはこちらの大栗の記事を御覧ください。
この機能が発表されてからちょっとしたアイデアが頭に浮かんでいまして、日常業務にかまけてそのままタスクの隅に追いやられていたのですが、昨日「AWS Black Belt Tech Webinar」というAWSサービスの使い方やTipsなどをレクチャーしてくれるWebストリーミングを見ていまして、ちょうどテーマが「S3」だったので思い出して質問してみました。
S3のクロスリージョンレプリケーションでレプリケーション先のリージョンがLambdaが使えるリージョンの場合、レプリケーションでLambdaは発火しますか?
LambdaはGA(Generally Available : 一般公開)になり、弊社でもちょこちょこ実案件に使い出しているのですが、その時に一番ネックになるのは「lambdaは東京リージョンに適応していない」というところなんですね。例えばS3のバケットにファイルがアップロードされたことをトリガーにLambdaを走らせて何らかの処理をする、という場合前提条件として「S3のバケットはUSスタンダードかOregonリージョンに作る」となるわけです。Lambdaを使いたい為だけに他のサービスは東京リージョンで統一しているのにS3バケットだけOregonリージョン、という気持ち悪い構成を甘受しなければいけませんでした。 もしレプリケーションでLambdaが発火するのであれば、管理は東京リージョンのバケットでイケるのではないか、というのがアイデアだったんです。 私の質問に対してAWSの中の人の答えは
えー、こちら、発火します。面白そうなのでぜひやってみてください。
とのこと。これはやってみるしかないでしょう。
やってみた
ということでやってみようと思います。今回は東京リージョンとUSスタンダードリージョンのそれぞれのバケットをCross-Region Replicationで結んで、東京リージョンのバケットにオブジェクトを入れ、USスタンダードリージョンのバケットにトリガーをつけてLambdaを発火、以前の記事で書いたTTLの変更処理をやってみようと思います。
まずはS3にバケットを2つ作成します。一つは東京リージョン、もうひとつはUSスタンダードリージョンに立てます。わかりやすいように名前を「chao2suketest-tokyo」「chao2suketest-us」とします。
次にchao2suketest-tokyoを選択し、「クロスリージョン レプリケーション」を選択します。クロスリージョンレプリケーションを使うにはバージョニングが有効になっている必要があるので、バージョニングを有効にします。
バージョニングを有効にしたらクロスリージョンレプリケーションの設定に入ります。送信先にUSスタンダードリージョンのバケットを選択します。S3から操作できるようにIAMロールを作成します。
IAMロールは東京リージョンのget系とlist系、USスタンダードリージョンのレプリケーションに対して許可を与えています。デフォルトでこのポリシーが設定されているので、何も考えずに[許可]ボタンを押します。
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:Get*", "s3:ListBucket" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::chao2suketest-tokyo", "arn:aws:s3:::chao2suketest-tokyo/*" ] }, { "Action": [ "s3:ReplicateObject", "s3:ReplicateDelete" ], "Effect": "Allow", "Resource": "arn:aws:s3:::chao2suketest-us/*" } ] }
ここまで設定して[保存]ボタンを押すとこんなエラーメッセージが出ます。
このエラーはクロスリージョンレプリケーションではあるあるになるくらい有名なエラーですね。レプリケーション先のバージョニングが有効化されていない時に出るエラーです。USスタンダードリージョンの方のバケット[chao2suketest-us]のバージョニングを有効にしてもう一度保存を押すと、きちんと設定されます。
まずレプリケーションがきちんと効いているかどうか確認してみましょう。東京リージョンにテスト用のファイルを一つアップロードします。
[chao2suketest-us]を見てみると、同じファイルがありました。レプリケーションはきちんと働いているようですね。
次にこの[chao2suketest-us]に対してLambdaを書いてつなげてみたいと思います。新規でLambda functionを作成、名前は[cmS3toLambdaTest]としました。以前書いたLambdaの記事を参考にこのようなLambdaを書いてみました。
console.log('Loading event'); var aws = require('aws-sdk'); var s3 = new aws.S3({apiVersion: '2006-03-01'}); var s3tokyo = new aws.S3({apiVersion: '2006-03-01', endpoint: 'https://s3-ap-northeast-1.amazonaws.com'}); exports.handler = function(event, context) { console.log('Received event:'); console.log(JSON.stringify(event, null, ' ')); // Get the object from the event and show its content type var bucket = event.Records[0].s3.bucket.name; var bucket2 = "chao2suketest-tokyo"; var key = event.Records[0].s3.object.key; s3.getObject({Bucket:bucket, Key:key}, function(err,data) { if (err) { console.log('error getting object ' + key + ' from bucket ' + bucket + '. Make sure they exist and your bucket is in the same region as this function.'); context.done('error','error getting file'+err); } else { console.log('logging Cache-Control : ',data.CacheControl); if (typeof data.CacheControl != 'undefined'){ console.log('cache control was already exists.'); context.done(null,'skip execution.'); }else { var ttl = 10; var params = { Bucket: bucket, /* required */ CopySource: bucket + "/" + key, Key: key, /* required */ CacheControl: "max-age=" + ttl, MetadataDirective: "REPLACE", ContentType: data.ContentType }; console.log('replace object.'); console.log('bucket : ' + bucket); console.log('CopySource : ' + bucket + "/" + key); s3tokyo.copyObject(params, function(err2, data2){ if (err2){ context.done('error','error2 getting file '+err2); }else{ console.log('replace done! Cache-Control : ',data2.CacheControl); Context.done(null,'object meta-data changed.'); } }); } } } ); };
バケットにオブジェクトが入ったらcache-controlとしてmax-age=10を設定する、というものです。書き終わったらレプリケーション先のバケットとつなげます。トリガーとなるイベントタイプはレプリケーションにてオブジェクトが登録された時、ということで「PUT」を選択します。
これでLambdaの設定も完了です。設定し終わるとこんな感じになります。
では早速実験してみましょう。東京リージョンの方に新しいファイルをアップロードしてみます。
USスタンダードリージョンのバケットを覗いてみると、、、max-age=10が登録されています!!
LambdaのログはCloudWatch Logsに吐かれますのでそちらを確認してみます。確かにLambdaが走っていますね。
応用編
ここからはもう少しLambdaを触ってみましょう。今のこの設定ですとUSスタンダードリージョンにはCache-Controlがつくものの、元の東京リージョンの方にはCache-Controlはつきません。あくまでレプリケーションしたファイルに対して操作しているので当然と言えば当然ですね。
でもここで思い出してみると、元々レプリケーションでLambdaが働くかどうか興味をもったのは「Lambdaを動かすためだけにS3のバケットをUSリージョンにするのが気持ち悪い」という動機だったはずです。 となるとそもそも東京リージョンに対して処理ができてなくてはいけないのではないでしょうか。またUSリージョンに入っているファイルはそもそも要らないんじゃないでしょうか。
ということでLambdaをこのように書き換えてみました。
console.log('Loading event'); var aws = require('aws-sdk'); var s3 = new aws.S3({apiVersion: '2006-03-01'}); var s3tokyo = new aws.S3({apiVersion: '2006-03-01', endpoint: 'https://s3-ap-northeast-1.amazonaws.com'}); exports.handler = function(event, context) { console.log('Received event:'); console.log(JSON.stringify(event, null, ' ')); // Get the object from the event and show its content type var bucket = event.Records[0].s3.bucket.name; var bucket2 = "chao2suketest-tokyo"; var key = event.Records[0].s3.object.key; s3.getObject({Bucket:bucket, Key:key}, function(err,data) { if (err) { console.log('error getting object ' + key + ' from bucket ' + bucket + '. Make sure they exist and your bucket is in the same region as this function.'); context.done('error','error getting file'+err); } else { console.log('logging Cache-Control : ',data.CacheControl); if (typeof data.CacheControl != 'undefined'){ console.log('cache control was already exists.'); context.done(null,'skip execution.'); }else { var ttl = 10; var params = { //Bucket: bucket, /* required */ Bucket: bucket2, //CopySource: bucket + "/" + key, CopySource: bucket2 + "/" + key, Key: key, /* required */ CacheControl: "max-age=" + ttl, MetadataDirective: "REPLACE", ContentType: data.ContentType }; console.log('replace object.'); console.log('bucket : ' + bucket2); console.log('CopySource : ' + bucket2 + "/" + key); s3tokyo.copyObject(params, function(err2, data2){ if (err2){ context.done('error','error2 getting file '+err2); }else{ console.log('replace done! Cache-Control : ',data2.CacheControl); var param2 = { Bucket: bucket, Key: key }; console.log('delete target object.'); s3.deleteObject(param2, function(err3, data3){ if (err3){ context.done('error','error3 getting file '+err3.stack); }else{ console.log('delete done!',null); context.done(null,'object meta-data changed.'); } }); } }); } } } ); };
Cache-controlをつけるのは東京リージョンのオブジェクトにして、Cache-ControlをつけたらUSリージョン側のオブジェクトを削除する、という実装にしてみました。 ちなみにこの場合はLambdaのRoleにはDeleteObject権限も必要になるので注意してください。あ、cross-region replicationのロールではなく、Lambdaのロールです。勘違いのないように。Lambdaのロールはこのようなポリシーに変えています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:*" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
では実験してみましょう。まずはUSリージョン側のオブジェクトを全て削除します。
次に東京リージョン側に新しいファイルをアップロードします。
USリージョン側を確認してみると、ファイルが何もありません。
東京リージョン側を確認してみると、先程アップロードしたファイルにcache-controlがついています。成功です!
CloudWatch Logsを確認してみると、綺麗にLambdaが走っていることがわかります。
まとめ
いかがでしょうか。実はこれで東京リージョンのコストだけでLambdaが使えるな、、、と思っていたのですが、よく考えるとバージョニングしているのですから削除してもコストはかかります。というか実案件でこんな回りくどい方法を使うのなら素直にUSリージョン使うだろ、という気になってきましたが、まあそこは実験、ということで。色々機能を組み合わせると何か別のものが見えてくるかも、しれませんしね。